package org.elephantt.webby.ibatis;

import com.ibatis.sqlmap.client.SqlMapClient;
import com.ibatis.sqlmap.client.SqlMapClientBuilder;
import com.ibatis.sqlmap.engine.datasource.DataSourceFactory;

import javax.sql.DataSource;
import java.util.Properties;
import java.util.Map;
import java.util.HashMap;
import java.io.InputStream;
import java.io.ByteArrayInputStream;

/**
 * Simple factory for iBatis SqlMapClients. iBatis has very poor programmatic configuration, so this is a hack that
 * allows application-created data sources to be used.
 */
public class IbatisFactory implements DataSourceFactory {
  // DataSources are ID'd, starting from 0 and increasing. This is what the next DataSource ID will be.
  private static Integer nextDataSourceId = 0;

  // map of DataSource ID to DataSource
  private static final Map<Integer, DataSource> datasources = new HashMap<Integer, DataSource>();

  // ID of the DataSource for this factory instance
  private Integer dataSourceId;

  /**
   * Creates a SqlMapClient with the given datasource and query file. The client should only call this once per
   * DataSource and store the resulting SqlMapClient.
   * @param dataSource
   * @param queryFilename
   * @return
   */
  public static SqlMapClient createSqlMapClient(DataSource dataSource, String queryFilename) {
    if (dataSource == null) throw new IllegalArgumentException("dataSource must be non-null");
    if (queryFilename == null) throw new IllegalArgumentException("queryFilename must be non-null");

    synchronized (IbatisFactory.class) {
      Integer dsId = nextDataSourceId++;
      datasources.put(dsId, dataSource);
      Properties prop = new Properties();
      prop.put("dsId", dsId.toString());
      prop.put("queryFile", queryFilename);
      return SqlMapClientBuilder.buildSqlMapClient(getIbatisInputStream(), prop);
    }
  }

  private static InputStream getIbatisInputStream() {
    return new ByteArrayInputStream(("<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n" +
            "<!DOCTYPE sqlMapConfig PUBLIC \"-//ibatis.apache.org//DTD SQL Map Config 2.0//EN\" \"http://ibatis.apache.org/dtd/sql-map-config-2.dtd\">\n" +
            "<sqlMapConfig>\n" +
            "  <settings useStatementNamespaces=\"false\" cacheModelsEnabled=\"true\"/>\n" +
            "\n" +
            "  <transactionManager type=\"JDBC\" commitRequired=\"false\">\n" +
            "    <dataSource type=\"org.elephantt.webby.ibatis.IbatisFactory\">\n" +
            "<property name=\"dsId\" value=\"${dsId}\"/>" +
            "</dataSource>" +
            "  </transactionManager>\n" +
            "\n" +
            "  <sqlMap resource=\"${queryFile}\"/>\n" +
            "</sqlMapConfig>").getBytes());
  }

  /**
   * iBatis will call this after construction.
   * @param map
   */
  public void initialize(Map map) {
    String dsIdStr = (String) map.get("dsId");
    assert dsIdStr != null : "name property was not set";
    try {
      dataSourceId = Integer.parseInt(dsIdStr);
    } catch (NumberFormatException e) {
      throw new RuntimeException(dsIdStr + " was not converible to an Integer", e);
    }
  }

  public DataSource getDataSource() {
    DataSource rv = datasources.get(dataSourceId);
    assert rv != null : "DataSource for ID " + dataSourceId + " did not exist";
    return rv;
  }
}